﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Text.RegularExpressions;
using System.Timers;
using System.Net.Sockets;

namespace TCP_UDP_client_server_test
{
    

    class TcpTestClient
    {
        private enum PacketType { dataPacket, heartbeatPacket };

        static private bool run = true;

        static private string ipString = "127.0.0.1";
        static private int port = 5000;
        static private int dataPeriod = 200; //in milliseconds; every dataPeriod millseconds, a data packet is sent
        static private int dataSize = 100; //in bytes; every packet is dataSize bytes
        static private int heartbeatPeriod = 50; //in milliseconds; every heartBeat milliseconds, a heartbeat packet is sent
        static private int heartbeatSize = 10; //in bytes; every packet is heartbeatSize bytes
        static private int secondsPassed = 0;
        
        static private TcpClient client;
        static private NetworkStream TcpStream;
        
        static private Queue<PacketType> sendQueue = new Queue<PacketType>();
        static private byte[] dataPacket;
        static private byte[] heartbeatPacket;
        
        private static System.Threading.Mutex queueMutex = new System.Threading.Mutex();

        private static void GetIP()
        {
            bool ipAcquired = false;
            while ( !(ipAcquired) )
            {
                Console.WriteLine("IP address of the server [{0}]: ", ipString);
                string ipInput = Console.ReadLine();

                if (ipInput.Length > 0)
                {
                    Match match = Regex.Match(ipInput, @"\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b");
                    if (match.Success)
                    {
                        ipString = ipInput;
                        ipAcquired = true;
                    }
                }
                else
                {
                    ipAcquired = true;
                }
            }
        }

        private static void GetPort()
        {
            bool portAcquired = false;
            while (!(portAcquired))
            {
                Console.WriteLine("Port to connect to [{0}]: ", port);
                string portInput = Console.ReadLine();

                if (portInput.Length > 0)
                {
                    int dummy;
                    if(int.TryParse(portInput, out dummy))
                    {
                        if ( (dummy < 65535) && (dummy > 1024))
                        {
                            port = dummy;
                            portAcquired = true;
                        }
                    }
                }
                else
                {
                    portAcquired = true;
                }
            }
        }

        private static void GetDataPeriod()
        {
            bool dataPeriodAcquired = false;
            while (!(dataPeriodAcquired))
            {
                Console.Write("Period at which to send data packets, in milliseconds [{0}]: ", dataPeriod);
                string portInput = Console.ReadLine();

                if (portInput.Length > 0)
                {
                    int dummy;
                    if (int.TryParse(portInput, out dummy))
                    {
                        if (dummy >= 0)
                        {
                            dataPeriod = dummy;
                            dataPeriodAcquired = true;
                        }
                    }
                }
                else
                {
                    dataPeriodAcquired = true;
                }
            }

        }

        private static void GetDataSize()
        {
            bool dataSizeAcquired = false;
            while (!(dataSizeAcquired))
            {
                Console.Write("Data packetz size, in bytes [{0}]: ", dataSize);
                string dataSizeInput = Console.ReadLine();

                if (dataSizeInput.Length > 0)
                {
                    int dummy;
                    if (int.TryParse(dataSizeInput, out dummy))
                    {
                        if (dummy >= 0)
                        {
                            dataSize = dummy;
                            dataSizeAcquired = true;
                        }
                    }
                }
                else
                {
                    dataSizeAcquired = true;
                }
            }

        }

        private static void GetHeartbeatPeriod()
        {
            bool heartbeatPeriodAcquired = false;
            while (!(heartbeatPeriodAcquired))
            {
                Console.Write("Period at which to send heartbeat packets, in milliseconds [{0}]: ", heartbeatPeriod);
                string heartPerInput = Console.ReadLine();

                if (heartPerInput.Length > 0)
                {
                    int dummy;
                    if (int.TryParse(heartPerInput, out dummy))
                    {
                        if(dummy >= 0)
                        {
                            heartbeatPeriod = dummy;
                            heartbeatPeriodAcquired = true;
                        }
                    }
                }
                else
                {
                    heartbeatPeriodAcquired = true;
                }
            }
        }

        private static void GetHeartbeatSize()
        {
            bool heartbeatSizeAcquired = false;
            while (!(heartbeatSizeAcquired))
            {
                Console.Write("Heartbeat packetz size, in bytes [{0}]: ", heartbeatSize);
                string dataSizeInput = Console.ReadLine();

                if (dataSizeInput.Length > 0)
                {
                    int dummy;
                    if (int.TryParse(dataSizeInput, out dummy))
                    {
                        if (dummy >= 0)
                        {
                            heartbeatSize = dummy;
                            heartbeatSizeAcquired = true;
                        }
                    }
                }
                else
                {
                    heartbeatSizeAcquired = true;
                }
            }

        }
        
        /// <summary>
        /// collects user's connection and test options (e.g. IP, ports, packet sizes, etc)
        /// </summary>
        private static void GetConnectionOptions()
        {
            GetIP();
            GetPort();
            GetDataPeriod();
            GetDataSize();
            GetHeartbeatPeriod();
            GetHeartbeatSize();
        }

        public static void connect()
        {
            GetConnectionOptions();

            heartbeatPacket = new byte[heartbeatSize];
            for (int ii = 0; ii < heartbeatPacket.Length; ii++)
            {
                heartbeatPacket[ii] = 0;
            }

            dataPacket = new byte[dataSize];
            for (int jj = 0; jj < dataPacket.Length; jj++)
            {
                dataPacket[jj] = 255;
            }

            Console.CancelKeyPress += new ConsoleCancelEventHandler(CtrlCPress);

            Timer secondTickTimer = new System.Timers.Timer(1000);
            secondTickTimer.Elapsed +=new ElapsedEventHandler(secondsDisplay);


            Timer heartbeatTimer = new System.Timers.Timer(heartbeatPeriod);
            heartbeatTimer.Elapsed += new ElapsedEventHandler(HeartbeatEnqueue);

            Timer dataTimer = new System.Timers.Timer(dataPeriod);
            dataTimer.Elapsed += new ElapsedEventHandler(DataEnqueue);

            heartbeatTimer.Start();
            dataTimer.Start();

            PacketType packet = new PacketType();

            openTCP();
            sendParameters();
            secondTickTimer.Start();
            Console.Write("\n{0}", secondsPassed);
            while (run)
            {

                if (sendQueue.Count > 0)
                {
                    queueMutex.WaitOne();
                    packet = sendQueue.Dequeue();

                    switch (packet)
                    {
                        case PacketType.dataPacket:
                            sendPacket(dataPacket);
                            break;
                        case PacketType.heartbeatPacket:
                            sendPacket(heartbeatPacket);
                            break;
                        default:
                            break;
                    }


                    queueMutex.ReleaseMutex();
                }
            }

            closeTCP();
        }

        protected static void CtrlCPress(object sender, ConsoleCancelEventArgs args)
        {
            run = false;
        }

        private static void secondsDisplay(object source, ElapsedEventArgs e)
        {
            secondsPassed++;
            Console.SetCursorPosition(0, Console.CursorTop);
            Console.Write("{0}", secondsPassed);
        }

        private static void HeartbeatEnqueue(object source, ElapsedEventArgs e)
        {
            queueMutex.WaitOne();
            PacketType ht = new PacketType();
            ht = PacketType.heartbeatPacket;
            sendQueue.Enqueue(ht);
            queueMutex.ReleaseMutex();
        }

        private static void DataEnqueue(object source, ElapsedEventArgs e)
        {
            queueMutex.WaitOne();
            PacketType dt = new PacketType();
            dt = PacketType.dataPacket;
            sendQueue.Enqueue(dt);
            queueMutex.ReleaseMutex();
        }

        private static void sendPacket(byte[] packet)
        {
            try
            {
                TcpStream.Write(packet, 0, packet.Length);
            }
            catch (Exception e)
            {
                Console.WriteLine("Error:{0}", e.ToString());
                run = false;
            }
        }

        private static void openTCP()
        {
            client = new TcpClient(ipString, port);
            TcpStream = client.GetStream();
        }

        private static void closeTCP()
        {
            TcpStream.Close();
            client.Close();
        }

        private static void sendParameters()
        {
            try
            {
                byte[] tempBuffer = new byte[4];

                tempBuffer = BitConverter.GetBytes(dataSize);
                TcpStream.Write(tempBuffer, 0, 4);

                tempBuffer = BitConverter.GetBytes(dataPeriod);
                TcpStream.Write(tempBuffer, 0, 4);

                tempBuffer = BitConverter.GetBytes(heartbeatSize);
                TcpStream.Write(tempBuffer, 0, 4);

                tempBuffer = BitConverter.GetBytes(heartbeatPeriod);
                TcpStream.Write(tempBuffer, 0, 4);
            }
            catch (Exception e)
            {
                Console.WriteLine("Error:{0}", e.ToString());
                run = false;
            }
        }
    }
}
